Skip to content

Add replace-label safe-outputs type (experimental)#40423

Merged
pelikhan merged 26 commits into
mainfrom
copilot/implement-replace-label-safe-outputs
Jun 23, 2026
Merged

Add replace-label safe-outputs type (experimental)#40423
pelikhan merged 26 commits into
mainfrom
copilot/implement-replace-label-safe-outputs

Conversation

Copilot AI commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Adds a new replace-label safe-outputs type that transitions label state in a single REST API call — removing one label and adding another atomically, eliminating the race window present when using remove-labels + add-labels in sequence.

Experimental: Using replace-label emits a compiler warning ("Using experimental feature: replace-label"), consistent with other experimental safe-output types such as dispatch_repository and merge-pull-request.

New type: replace-label

safe-outputs:
  replace-label:
    allowed-add: ["approved", "done"]
    allowed-remove: ["in-review", "pending"]
    required-labels: ["triage"]
    allowed-transitions:
      - from: "in-review"
        to: "approved"
      - from: "pending"
        to: "in-review"

The agent calls replace_label(label_to_remove, label_to_add). The allowed-add/allowed-remove/blocked/required-labels/required-title-prefix guards work identically to add-labels and remove-labels. The optional allowed-transitions list further restricts mutations to an explicit state machine — only the listed (from, to) pairs are permitted when it is configured.

Implementation

  • pkg/workflow/replace_label.goReplaceLabelConfig struct (BaseSafeOutputConfig + SafeOutputTargetConfig + SafeOutputFilterConfig + AllowedAdd/AllowedRemove/Blocked + AllowedTransitions); LabelTransition struct (From/To)
  • JS handler (replace_label.cjs) — uses a single issues.setLabels() REST call (PUT /repos/{owner}/{repo}/issues/{issue_number}/labels) to atomically replace the label set; computes the new set as (current_labels − label_to_remove) ∪ {label_to_add}; proceeds with add-only when label_to_remove is absent from the item; validates each label with explicit allowlist/blocklist checks (no silent filtering or name truncation); validates (label_to_remove, label_to_add) pair against allowed-transitions after blocked/allowlist guards
  • Schema — full replace-label entry in main_workflow_schema.json including the new allowed-transitions array property with from/to required fields
  • Experimental warningpkg/workflow/compiler_validators.go emits "Using experimental feature: replace-label" on stderr when the type is configured; covered by replace_label_experimental_warning_test.go
  • Infrastructure wiring — handler descriptor, registry (serialises transitions as []map[string]string), validation config, tools computation, repo params, max validation, prompt step budget, tool description enhancer (13 files, following established pattern)

Specification

  • specs/replace-label-spec.md — W3C-style specification (v1.0.1) covering the 8-stage processing pipeline, config/message schemas, REST interface (setLabels) and atomicity semantics, error-handling categories (soft-skip vs hard-error), security considerations, and compliance test IDs mapped to normative requirement codes
  • .github/aw/safe-outputs-management.md — added replace-label documentation entry alongside add-labels/remove-labels
  • .github/aw/syntax-agentic.md — added replace-label to the safe-outputs type reference list

pr-sous-chef: requested branch update from run 27914085128.

Generated by 👨‍🍳 PR Sous Chef · 47 AIC · ⌖ 0.983 AIC · ⊞ 17.3K ·


pr-sous-chef run https://github.com/github/gh-aw/actions/runs/27919051457

Generated by 👨‍🍳 PR Sous Chef · 77.8 AIC · ⌖ 1.68 AIC · ⊞ 17.3K ·

Copilot AI and others added 3 commits June 20, 2026 03:28
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…call signatures

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot update specification with w3c spec writer. then apply specification to new code.

Copilot AI and others added 3 commits June 20, 2026 15:13
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
… handling

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jun 20, 2026

Copy link
Copy Markdown
Contributor Author

@copilot update specification with w3c spec writer. then apply specification to new code.

Done. Here's what was produced and applied:

Spec (specs/replace-label-spec.md, 849 lines, v1.0.0 Candidate Recommendation):

  • §4 Data Model — YAML config and JSON message schemas with normative field tables
  • §5 Processing Model — 8-stage pipeline diagram (schema validation → count gate → target resolution → label validation → gate checks → staged mode → label resolution/auto-creation → GraphQL mutation)
  • §6 GraphQL Interface — normative mutation definition with atomicity semantics (RL-043)
  • §7 Error Handling — 10 error categories with soft-skip vs hard-error classification
  • §8 Security Considerations — allowlist enforcement, cross-repo restrictions, auto-creation gating, staged mode as a security control
  • §9 Compliance Testing — 44 test IDs (T-RL-001 – T-RL-072) mapped to requirement codes RL-001 – RL-056

Spec applied to code:

  • .github/aw/safe-outputs-management.md — added replace-label entry (was missing)
  • .github/aw/syntax-agentic.md — added replace-label to the safe-outputs type list
  • actions/setup/js/replace_label.cjs — implemented RL-046 (partial mutation failure detection): when removeLabelsFromLabelable succeeds but addLabelsToLabelable fails, the error log now clearly distinguishes partial from total failure rather than emitting a generic message

@pelikhan pelikhan marked this pull request as ready for review June 20, 2026 15:23
Copilot AI review requested due to automatic review settings June 20, 2026 15:23
@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (241 new lines across business-logic directories) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/40423-add-replace-label-safe-output-type.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff (new replace-label safe-output type, atomic GraphQL label transition).
  2. Complete the missing sections — confirm the inferred decision rationale, refine the alternatives, and add any context the AI couldn't infer (e.g. why GraphQL sequential-within-request was deemed acceptable vs. true atomicity).
  3. Commit the finalized ADR to docs/adr/ on your branch (it is already there as a Draft).
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-40423: Add replace-label Safe-Output Type

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 40423-add-replace-label-safe-output-type.md for PR #40423).

🔒 This PR cannot merge until an ADR is linked in the PR body.

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 110.3 AIC · ⌖ 10.5 AIC · ⊞ 6.9K ·

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /tdd and /grill-with-docs — leaving as COMMENT; no blocking bugs found but three test gaps are worth closing before this ships.

📋 Key Themes & Highlights

Key Themes

  • Dead code: RESOLVE_LABEL_QUERY — defined, exported, and documented but never called. The implementation uses per-label REST calls instead of this bulk GraphQL fetch. Either remove it or use it.
  • Test coverage gaps: The spec defines 44 normative test IDs (T-RL-001–T-RL-072); the test file has 13 test cases. Three specific gaps stand out: (1) RL-046 partial mutation failure, (2) createLabel failure path, and (3) no test exercises the GraphQL mutation path at all (since RESOLVE_LABEL_QUERY is unused).
  • Fragile 404 detection: resolveOrCreateLabel checks msg.includes("404") rather than err.status === 404 — the latter is more reliable.
  • Hash entropy: deterministicLabelColor consumes only 18 of 32 hash bits, increasing collision probability among label names. Minor visual issue.

Positive Highlights

  • ✅ Excellent wiring consistency — all 12 Go infrastructure files follow the exact pattern of add-labels/remove-labels, making the Go side very low-risk.
  • ✅ The single-request GraphQL mutation correctly achieves the atomicity goal stated in the PR description.
  • ✅ Graceful handling of missing label_to_remove (proceeds with add-only) is correctly implemented and tested.
  • ✅ Staged-mode preview is implemented and tested.
  • ✅ Per-repo label node-ID cache (repoCaches) correctly avoids redundant REST calls across multiple messages in one run.
  • ✅ The W3C-style spec is thorough and provides a solid compliance baseline for future testing.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 99.8 AIC · ⌖ 8.99 AIC · ⊞ 6.9K

Comment thread actions/setup/js/replace_label.cjs Outdated
*
* @type {string}
*/
const RESOLVE_LABEL_QUERY = /* GraphQL */ `

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/grill-with-docs] RESOLVE_LABEL_QUERY is defined, exported, and documented — but never called. The implementation resolves labels via rest.issues.getLabel (one REST call per label) instead of this bulk GraphQL query.

💡 Options

Either:

  1. Remove the constant and its export — it is dead code that misleads readers into thinking GraphQL label resolution is the implementation path.
  2. Use it — replace the per-label REST call in resolveOrCreateLabel with a single RESOLVE_LABEL_QUERY call fetching all repo labels up-front, then look up by name (more efficient for repos adding many labels in one run).

The current export in module.exports implies a public API contract, but no test exercises the GraphQL label-resolution path at all.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: RESOLVE_LABEL_QUERY constant and its module.exports entry have been removed. The module now exports only { main, REPLACE_LABEL_MUTATION }.

Comment thread actions/setup/js/replace_label.cjs Outdated
return label.node_id;
} catch (err) {
const msg = getErrorMessage(err);
if (!msg.includes("404") && !msg.toLowerCase().includes("not found")) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] 404 detection relies on matching error message strings rather than the HTTP status code, making it fragile if the underlying client changes its error format.

💡 Suggested fix

Instead of (or in addition to) string-matching, check err.status or err.status === 404 which Octokit/REST sets reliably:

if (err.status !== 404) {
  throw err;
}

This is more robust than msg.includes("404") which could false-positive on a label named "404" or a repo description containing that string.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: string-matching replaced with err?.status !== 404 for reliable structured status detection.

Comment thread actions/setup/js/replace_label.cjs Outdated
// RL-046: detect partial mutation success — remove succeeded but add failed
const errAsAny = /** @type {any} */ err;
const partialData = errAsAny?.data;
if (partialData?.removeLabels && !partialData?.addLabels) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The RL-046 partial mutation failure branch (remove succeeded, add failed) is explicitly called out in the spec and has dedicated error messaging — but it has no corresponding test case. This is the most important missing test given the spec's normative requirement for it.

💡 Suggested test
it("should log partial failure when remove succeeds but add fails (RL-046)", async () => {
  const gqlError = new Error("add mutation failed");
  gqlError.data = {
    removeLabels: { clientMutationId: null }, // remove succeeded
    // addLabels is absent — simulates partial failure
  };
  mockGithub.graphql = async () => { throw gqlError; };

  const handler = await main({});
  const result = await handler({ label_to_remove: "in-progress", label_to_add: "done" }, {});

  expect(result.success).toBe(false);
  expect(mockCore.errors.some(e => e.includes("in-progress") && e.includes("done"))).toBe(true);
});

Without this test, a future refactor of the error-handling path could silently break the spec compliance obligation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the RL-046 partial-failure test ("should log partial failure and return error when remove succeeds but add fails (RL-046)"). Also added a comment explaining that withRetry's enhanceError wraps the original error, so the detection checks both err.data (direct graphql error) and err.originalError.data (wrapped).

Comment thread actions/setup/js/replace_label.test.cjs Outdated
expect(result.labelAdded).toBe("done");
});

it("should create label_to_add if it does not exist in the repo", async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The label auto-create test covers the happy path (404 → createLabel succeeds) but not the failure path (404 → createLabel also throws). Missing this leaves the error propagation from resolveOrCreateLabel untested.

💡 Suggested test
it("should return error when label_to_add cannot be created in the repo", async () => {
  mockGithub.rest.issues.getLabel = async () => {
    const err = new Error("Not Found");
    err.status = 404;
    throw err;
  };
  mockGithub.rest.issues.createLabel = async () => {
    throw new Error("Resource not accessible by integration");
  };

  const handler = await main({});
  const result = await handler({ label_to_remove: "in-progress", label_to_add: "needs-review" }, {});

  expect(result.success).toBe(false);
  expect(result.error).toContain("Failed to resolve/create label");
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-creation has been removed entirely: the handler now fails with a hard error when label_to_add is not found (404), consistent with add_labels. The test was updated to "should return error when label_to_add does not exist in the repo", and a separate "should return error when getLabel fails with a non-404 error" test was also added.

Comment thread actions/setup/js/replace_label.cjs Outdated
}
const r = 128 + (hash & 0x3f);
const g = 128 + ((hash >> 6) & 0x3f);
const b = 128 + ((hash >> 12) & 0x3f);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/grill-with-docs] The hash function uses only 18 bits of the 32-bit integer (bits 0–5 for R, 6–11 for G, 12–17 for B), meaning any two label names whose hashes are identical in the low 18 bits will produce the same color regardless of how different they are in the high bits.

💡 Better entropy distribution

A simple improvement is to mix the high bits back in via XOR before extracting channels:

function deterministicLabelColor(name) {
  let hash = 0;
  for (let i = 0; i < name.length; i++) {
    hash = (hash * 31 + name.charCodeAt(i)) >>> 0;
  }
  // fold high bits into low bits for better distribution
  hash ^= (hash >>> 18);
  const r = 128 + (hash & 0x3f);
  const g = 128 + ((hash >> 6) & 0x3f);
  const b = 128 + ((hash >> 12) & 0x3f);
  return ((r << 16) | (g << 8) | b).toString(16).padStart(6, "0");
}

This is a minor visual quality issue, not a correctness bug — but worth noting given the spec dedicates a subsection to label color determinism.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deterministicLabelColor has been removed entirely — label auto-creation was removed from the handler, so there is no longer a need for a color algorithm. The function and its test suite are gone.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new replace-label safe-output type that lets agents transition label state by removing one label and adding another via a single GraphQL request, aligning label-based state machines with safer (reduced-race) semantics.

Changes:

  • Adds replace-label configuration/parsing and wires it into safe-outputs enablement, validation, handler registry, tool computation, and prompt tooling.
  • Implements the JavaScript handler (replace_label.cjs) plus Vitest coverage for core behaviors (add-only when source label missing, auto-create target label, staged mode).
  • Extends schema and docs, including a detailed spec document and references in safe-outputs documentation.
Show a summary per file
File Description
specs/replace-label-spec.md Adds a formal spec for replace-label, including processing stages and conformance requirements.
pkg/workflow/unified_prompt_step.go Exposes the replace_label tool in the unified prompt tool list with max-budget integration.
pkg/workflow/tool_description_enhancer.go Adds constraint-enhancement text for replace_label tool descriptions.
pkg/workflow/safe_outputs_validation.go Includes replace-label in target validation wiring.
pkg/workflow/safe_outputs_validation_config.go Adds message schema validation config for replace_label.
pkg/workflow/safe_outputs_tools_repo_params.go Adds repo-parameter inference support for replace_label.
pkg/workflow/safe_outputs_tools_computation.go Enables replace_label in the computed tool set when configured.
pkg/workflow/safe_outputs_state.go Includes ReplaceLabel in safe-outputs enabled checks.
pkg/workflow/safe_outputs_max_validation.go Validates replace_label max field formatting/constraints.
pkg/workflow/safe_outputs_handler_registry.go Emits handler config for replace_label and ensures null/empty config enables the handler.
pkg/workflow/safe_outputs_config.go Parses replace-label from workflow frontmatter into SafeOutputsConfig.
pkg/workflow/safe_output_validation_config_test.go Updates validation-config JSON test expectations to include replace_label.
pkg/workflow/safe_output_handlers.go Registers replace-label as a safe-output handler descriptor with permissions gating.
pkg/workflow/replace_label.go Adds ReplaceLabelConfig and frontmatter parsing helper.
pkg/workflow/compiler_types.go Extends SafeOutputsConfig with ReplaceLabel.
pkg/parser/schemas/main_workflow_schema.json Adds the replace-label safe-outputs schema entry and related schema text updates.
actions/setup/js/replace_label.cjs Implements the replace_label handler (REST label lookup/creation + combined GraphQL mutation).
actions/setup/js/replace_label.test.cjs Adds unit tests for replace_label handler behavior.
.github/aw/syntax-agentic.md Adds replace-label to the safe-outputs type reference list.
.github/aw/safe-outputs-management.md Documents replace-label alongside existing label safe-outputs.
.changeset/replace-label.md Adds a minor-version changeset describing the new safe-output type.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 22/22 changed files
  • Comments generated: 5

Comment thread actions/setup/js/replace_label.cjs Outdated
Comment on lines +308 to +313
githubClient.graphql(REPLACE_LABEL_MUTATION, {
labelableId,
addLabelIds: [addLabelNodeId],
removeLabelIds: removeLabelNodeId ? [removeLabelNodeId] : [],
}),
RATE_LIMIT_RETRY_CONFIG,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed with @include(if: $doRemove). The mutation now includes a $doRemove: Boolean! variable; when label_to_remove is absent from the item, $doRemove is false and removeLabelsFromLabelable is omitted from the request entirely, avoiding any empty-labelIds issue.

Comment thread actions/setup/js/replace_label.cjs Outdated
Comment on lines +114 to +119
} catch (err) {
const msg = getErrorMessage(err);
if (!msg.includes("404") && !msg.toLowerCase().includes("not found")) {
throw err;
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: 404 detection now uses err?.status !== 404 (structured status check) instead of string-matching.

Comment thread actions/setup/js/replace_label.cjs Outdated
Comment on lines +224 to +236
// Validate label_to_remove against allowed-remove and blocked patterns
const removeValidation = validateLabels([labelToRemove], configAllowedRemove, 1, blockedPatterns);
if (!removeValidation.valid) {
core.warning(`label_to_remove validation failed: ${removeValidation.error}`);
return { success: false, error: removeValidation.error ?? "Invalid label_to_remove" };
}

// Validate label_to_add against allowed-add and blocked patterns
const addValidation = validateLabels([labelToAdd], configAllowedAdd, 1, blockedPatterns);
if (!addValidation.valid) {
core.warning(`label_to_add validation failed: ${addValidation.error}`);
return { success: false, error: addValidation.error ?? "Invalid label_to_add" };
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: replaced both validateLabels() calls with a new validateSingleLabel() function that (a) checks blocked patterns first, then the allowlist, (b) rejects with a specific named error rather than silently filtering, and (c) never truncates the label name.

Comment on lines +176 to +181
**RL-001**: Glob pattern matching for `allowed-add`, `allowed-remove`, and `blocked` MUST follow the semantics of the `gobwas/glob` library (as used elsewhere in gh-aw), where `*` matches any sequence of characters within a label name and `[...]` denotes a character class.

**RL-002**: A label name MUST match at least one pattern in an allowlist (`allowed-add` or `allowed-remove`) when the list is non-empty. An empty or absent allowlist permits any label name.

**RL-003**: A label name MUST NOT match any pattern in the `blocked` list. Blocklist evaluation MUST occur after allowlist evaluation. A label that passes the allowlist but matches a blocked pattern MUST be rejected.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in spec: RL-003 now reads "Blocklist evaluation MUST occur before allowlist evaluation (it is a security boundary). A label matching a blocked pattern MUST be rejected immediately, regardless of any allowlist entry." — matching the actual order in safe_output_validator.cjs and in the new validateSingleLabel().

Comment thread specs/replace-label-spec.md Outdated
Comment on lines +374 to +375
**RL-028**: Staged mode execution MUST return `{ success: true, staged: true }` from the handler, and MUST NOT decrement any rate-limit budget or increment the operation count.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in spec: RL-028 now documents the actual behavior — staged messages DO count toward max because createCountGatedHandler increments processedCount before delegating to the handler.

@github-actions

Copy link
Copy Markdown
Contributor

@copilot review all comments and address unresolved review feedback.

Generated by 👨‍🍳 PR Sous Chef · 40.6 AIC · ⌖ 1.08 AIC · ⊞ 17.3K ·

@github-actions

Copy link
Copy Markdown
Contributor

@copilot please refresh the branch state and share a short blocker summary once review feedback is handled.

Generated by 👨‍🍳 PR Sous Chef · 40.6 AIC · ⌖ 1.08 AIC · ⊞ 17.3K ·

@github-actions

Copy link
Copy Markdown
Contributor

@copilot review all comments and resolve the open review threads before refreshing the branch.

Generated by 👨‍🍳 PR Sous Chef · 74.2 AIC · ⌖ 1.85 AIC · ⊞ 17.3K ·

@github-actions

Copy link
Copy Markdown
Contributor

``
Please resolve the 15 open review threads, then refresh the branch and rerun checks.

Generated by 👨‍🍳 PR Sous Chef · 32.4 AIC · ⌖ 1.09 AIC · ⊞ 17.3K ·

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot merge main and lint js

Copilot AI and others added 2 commits June 22, 2026 22:04
…lace-label-safe-outputs

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…k + prettier conflict in replace_label.cjs

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

@copilot merge main and lint js

Done. Merged main and fixed the prettier lint failure in replace_label.cjs. The root cause was a prettier/TypeScript conflict — prettier was stripping the parentheses from JSDoc cast expressions, which caused the TS type errors to resurface. Replaced both cast patterns with safer restructured code (using cached !== undefined check for the cache lookup, and a get-or-init pattern for the repo cache) that satisfies both TypeScript and prettier. Both typecheck and lint now pass cleanly.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot mark safe output as experimental

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

@copilot mark safe output as experimental

Done in commit feat: mark replace-label safe output as experimental. Added replace-label to the experimental feature warnings in compiler_validators.go — it now emits "Using experimental feature: replace-label" on stderr when the type is configured, consistent with dispatch_repository and merge-pull-request. Added an integration test in replace_label_experimental_warning_test.go following the same pattern.

Copilot AI changed the title Add replace-label safe-outputs type Add replace-label safe-outputs type (experimental) Jun 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

@copilot review all comments and address unresolved review feedback.

Please resolve the dismissed review items, especially the auto-create side effect and fragile 404 handling.

Generated by 👨‍🍳 PR Sous Chef · 38.5 AIC · ⌖ 1.05 AIC · ⊞ 17.3K ·

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot ensure that the removal and addition is done in a single REST API CALL.

@github-actions

Copy link
Copy Markdown
Contributor

@copilot review all comments and address unresolved review feedback.

Generated by 👨‍🍳 PR Sous Chef · 26.3 AIC · ⌖ 1.05 AIC · ⊞ 17.3K ·

@github-actions

Copy link
Copy Markdown
Contributor

Please resolve the open review threads, then refresh the branch and re-check PR status.

Generated by 👨‍🍳 PR Sous Chef · 26.3 AIC · ⌖ 1.05 AIC · ⊞ 17.3K ·

…etLabels call

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

@copilot ensure that the removal and addition is done in a single REST API CALL.

Done in commit c391662. The handler now uses a single issues.setLabels() REST call (PUT /repos/{owner}/{repo}/issues/{issue_number}/labels) instead of a GraphQL mutation.

The new flow:

  1. issues.get() — already needed for filter checks; also provides the current label set
  2. Compute new label set: (current_labels − {label_to_remove}) ∪ {label_to_add} (deduped)
  3. issues.setLabels({ labels: newLabelNames }) — single REST call that atomically replaces the entire label set

This is more atomic than the former GraphQL approach: the PUT endpoint applies the new label state in one server-side operation, eliminating even the sequential-execution gap that existed between the two GraphQL root mutations. The resolveLabel/getLabel node-ID lookup is also gone. Tests and spec updated accordingly.

@github-actions

Copy link
Copy Markdown
Contributor

``
@copilot review all comments and address unresolved review feedback.

Generated by 👨‍🍳 PR Sous Chef · 49.3 AIC · ⌖ 1.07 AIC · ⊞ 17.3K ·

@github-actions

Copy link
Copy Markdown
Contributor

``
Please address the requested changes, then summarize the remaining blockers.

Generated by 👨‍🍳 PR Sous Chef · 49.3 AIC · ⌖ 1.07 AIC · ⊞ 17.3K ·

@pelikhan pelikhan merged commit 1d61f42 into main Jun 23, 2026
44 checks passed
@pelikhan pelikhan deleted the copilot/implement-replace-label-safe-outputs branch June 23, 2026 02:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants